﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading;

using Google.GData.Calendar;
using Google.GData.Extensions;

using Gsn.Base;
using Gsn.Base.Notification.Calendar;

namespace Gsn.Calendar.Engine
{
    public class CalendarEngine : ICalendarEngine
    {
        // model constants
        private const int NOTIFICATION_WAIT_MILLIS = 100;
        private const int UPDATE_WAIT_MILLIS = 30000; ///180000;

        private const int NUMBER_OF_LISTED_EVENTS = 100;
        private const int NUMBER_OF_EVENTS_TO_RETRIEVE_FOR_UPDATE = 2;

        private static readonly TimeSpan EVENT_TIMESPAN_FOR_UPDATE = new TimeSpan(8, 0, 0, 0);

        // threads shit...
        private Thread alarmNotificationThread_;
        private Thread updateThread_;
        private DateTime? lastUpdate_ = null; ///remember that we have to assign this...

        // helper object...
        private CalendarServiceWrapper calendarServiceWrapper_;

        // data about the next calendar event to be notified by the model...
        // these are shared among threads...
        private CalendarNotification nextCalendarNotification_;
        private DateTime? nextAlarmDateTime_ = null;

        // this tells us if we are running the model...
        private volatile bool running_ = false;

        #region ICalendarEngine Members

        public event CalendarNotificationHandler AlarmCalendarNotification;
        public event CalendarNotificationHandler UpdateAlarmCalendarNotification;
        public event CalendarNotificationHandler InformativeCalendarNotification;

        /// <summary>
        /// Search all matching calendar events and raise a notification C# event for all observers.
        /// </summary>
        /// <param name="fromDate">Starting date & time</param>
        /// <param name="toDate">Limit date & time</param>
        public void ListMatchingCalendarEvents(String titleOfList, DateTime fromDate, DateTime toDate)
        {
            if (!running_)
                return;

            ICollection<EventEntry> events;
            CalendarNotification calendarNotification;
            CalendarNotificationElement calendarNotificationElement;

            calendarNotification = new CalendarNotification(titleOfList, CalendarNotificationPriority.Low);

            events = calendarServiceWrapper_.GetCollectionOfNMatchingEventsForAllCalendars(
                fromDate,
                toDate,
                NUMBER_OF_LISTED_EVENTS);

            if(events != null)
                foreach (EventEntry eventEntry in events)
                {
                    try
                    {
                        calendarNotificationElement =
                            new CalendarNotificationElement(eventEntry.Title.Text,
                                eventEntry.Content.Content,
                                eventEntry.Times[0].StartTime,
                                eventEntry.Times[0].EndTime);

                        calendarNotification.Add(calendarNotificationElement);
                    }
                    catch (Exception e)
                    {
                        ///eat up errors "quietly" :S ...
                    }
                }

            RaiseCalendarNotificationHandler(this.InformativeCalendarNotification, calendarNotification);
        }

        #endregion

        #region IEngine Members

        public bool Running
        {
            get
            {
                return running_;
            }
        }

        public void StartEngine(string username, string password)
        {
            if (running_)
                return;

            calendarServiceWrapper_ = new CalendarServiceWrapper(username, password);

            alarmNotificationThread_ = new Thread(new ThreadStart(AlarmNotificationThreadStart));
            updateThread_ = new Thread(new ThreadStart(UpdateThreadStart));

            running_ = true;

            updateThread_.Start();
            alarmNotificationThread_.Start();
        }

        public void StopEngine()
        {
            if (!running_)
                return;

            running_ = false;

            alarmNotificationThread_.Abort();
            updateThread_.Abort();

            alarmNotificationThread_ = null;
            updateThread_ = null;

            calendarServiceWrapper_ = null;
        }

        #endregion

        #region Engine's specific methods...

        private void RaiseCalendarNotificationHandler(CalendarNotificationHandler toRaise, CalendarNotification calendarNotification)
        {
            if (!running_)
                return;

            if (toRaise != null)
                toRaise.BeginInvoke(this, calendarNotification, null, null);
        }

        private void AlarmNotificationThreadStart()
        {
            while (running_)
            {
                lock (this)
                {
                    if (nextCalendarNotification_ != null &&
                        nextAlarmDateTime_ != null &&
                        nextAlarmDateTime_ < DateTime.Now)
                    {
                        RaiseCalendarNotificationHandler(this.AlarmCalendarNotification, nextCalendarNotification_);

                        ///clear next calendar event
                        nextCalendarNotification_ = null;
                        nextAlarmDateTime_ = null;
                    }
                }
                Thread.Sleep(NOTIFICATION_WAIT_MILLIS);
            }
        }

        private void UpdateThreadStart()
        {
            while (running_)
            {
                CalendarFeed calendars = null;
                ICollection<EventEntry> calendarEvents = null;
                bool aCalendarChanged = false;

                lock (this)
                {
                    ///retrieve all calendars...
                    calendars = calendarServiceWrapper_.GetAllCalendars();

                    ///if there were no errors...
                    if (calendars != null)
                    {
                        if(lastUpdate_ != null) 
                            foreach (CalendarEntry calendar in calendars.Entries)
                                if (calendar.Updated > lastUpdate_)
                                {
                                    aCalendarChanged = true;
                                    break;
                                }

                        ///if we don't have a calendar notification marked as next or if a calendar changed...
                        if (nextCalendarNotification_ == null || aCalendarChanged)
                        {
                            ///search among N events from all calendars...
                            calendarEvents = calendarServiceWrapper_.GetCollectionOfNMatchingEventsForAllCalendars(
                                DateTime.Now,
                                DateTime.Now.Add(EVENT_TIMESPAN_FOR_UPDATE),
                                NUMBER_OF_EVENTS_TO_RETRIEVE_FOR_UPDATE);

                            SelectNextCalendarEvent(calendarEvents);

                            RaiseCalendarNotificationHandler(this.UpdateAlarmCalendarNotification, nextCalendarNotification_);
                        }
                    }

                    ///store the last model update time...
                    lastUpdate_ = DateTime.Now;
                }
                Thread.Sleep(UPDATE_WAIT_MILLIS);
            }
        }

        private void SelectNextCalendarEvent(ICollection<EventEntry> calendarEvents)
        {
            if (!running_)
                return;

            if (calendarEvents == null)
                return;

            TimeSpan alarmTimeSpan;
            CalendarNotification nextCalendarNotification = new CalendarNotification("Next Calendar Event Alarm...", CalendarNotificationPriority.High);
            DateTime? selectedAlarm = null;
            DateTime alarm1, alarm2;

            lock (this)
            {
                foreach (EventEntry eventEntry in calendarEvents)
                    if (eventEntry.Reminder != null &&
                        eventEntry.Reminder.Method != Reminder.ReminderMethod.none &&
                        eventEntry.Times != null &&
                        eventEntry.Times.Count > 0)
                    {
                        alarm1 = DateTime.MaxValue;

                        ///calculate sooner & valid alarm time for this event...
                        foreach (Reminder reminder in eventEntry.Reminders)
                        {
                            alarmTimeSpan = new TimeSpan(reminder.Days, reminder.Hours, reminder.Minutes, 0);
                            alarm2 = eventEntry.Times[0].StartTime.Subtract(alarmTimeSpan);
                            if (alarm2 > DateTime.Now && alarm1 > alarm2)
                                alarm1 = alarm2;
                        }

                        ///decide if we should select this calendar event as the next one to notify...
                        if (alarm1 > DateTime.Now && (selectedAlarm == null || alarm1 < selectedAlarm))
                        {
                            CalendarNotificationElement calendarNotificationElement = new CalendarNotificationElement(eventEntry.Title.Text,
                                    eventEntry.Content.Content,
                                    eventEntry.Times[0].StartTime,
                                    eventEntry.Times[0].EndTime);

                            nextCalendarNotification.Clear();
                            nextCalendarNotification.Add(calendarNotificationElement);

                            selectedAlarm = alarm1;
                        }
                    }

                nextCalendarNotification_ = nextCalendarNotification;
                nextAlarmDateTime_ = selectedAlarm;
            }
        }

        #endregion
    }
}
